最近終於完成公司新產品的前端開發啦!不過在開發時也遇到了不少挑戰,其中最難處理的就屬「MQTT通訊協定」的部分了...😅
而自己在study或是Google資料的過程中,發現「React.js + MQTT」的資訊好像比較少一些,因此也在這邊把自己的處理作法紀錄下來,之後若有更佳的作法也會再更新👍
MQTT是一種輕量級的通訊協定,目前主要在物聯網(IoT)領域中被廣泛使用。而其採用「Publish發佈/Subscribe訂閱」的模式,當發佈者將訊息發佈在一個Topic(主題)後,即透過Broker(訊息代理)轉發,讓所有已訂閱該Topic的訂閱者接收到訊息。
以上關於協定的簡介就先到這邊,更詳細的概念說明可以參考這一篇囉 ↓
https://resource.webduino.io/blog/mqtt-guide
一般情況是直接安裝官方的「MQTT.js」,但在React.js的專案裡使用,可能會產生不少Webpack環境以及執行上的錯誤。不過幸運的是,近期已有熱心人士提供修正完成的版本,因此若想節省時間、直接使用的話,建議可以安裝「precompiled-mqtt」。
npm i precompiled-mqtt
也提供MQTT.js官方文件於下方連結,內容中有各API的使用說明:
https://github.com/mqttjs/MQTT.js
由於主要為了測試MQTT的收發,就先用幾個簡單元件來建立demo頁面吧:
import { useEffect, useState } from 'react';
const MqttDemo = () => {
const [data, setData] = useState(
{
topic: '-',
message: '-',
}
);
return (
<div style={{width: '400px', margin: 'auto'}}>
<h3>
{`topic: ${data.topic}, Message: ${data.message}`}
</h3>
<button type='button'>
{'發佈(Publish)'}
</button>
</div>
)
};
export default MqttDemo;
import mqtt from 'precompiled-mqtt';
在沒有架設Broker的情況下,可以使用「Eclipse Mosquitto™」所提供的測試Server來實作收發功能( https://test.mosquitto.org/ )。
★ 不過要注意的是,由於瀏覽器(Browser)環境不支援mqtt通訊協定,因此Web應用程式須透過WebSocket協定來連線。將瀏覽器成為基於WebSocket的MQTT client(MQTT over WebScoket),才能直接接收資料。
找到Port 8081就是我們需要的「MQTT over WebScoket」後,使用connect API連線、即可取得一個Client EventEmitter(事件發射器)。
const client = mqtt.connect('https://test.mosquitto.org:8081/');
取得Client後,就可以透過「subscribe API」與「監聽message事件」,來訂閱指定主題與接收訊息。
在message事件的callback參數中,「topic」代表這則訊息是由哪個主題發送過來的,而「payload」則是其訊息內容。
而由於payload的資料型態是「Buffer」,所以需要使用toString()
將其轉換為字串。
const subMessage = (client) => {
client.subscribe('/test1103');
client.on('message', (topic, payload) => {
const data2String = payload.toString();
setData(
{
topic: topic,
message: JSON.parse(data2String)
}
);
});
};
最後將上方寫好function傳入useEffect。讓React在render完成後,直接開始訂閱與接收訊息,並在元件卸載時與Broker結束連線(若沒有結束連線,即便切換路徑也會持續收到訊息)。
e.g. 現在當有人把”Hello”訊息發佈到「test1103」這個主題,這邊的message事件就會觸發、並接收到”Hello”。
基本的MQTT連線/訂閱/接收,在這個階段就算是實作完成了。不過若沒有訊息發佈的話,其實也不易確認是否有正常運作吧~接著就直接實作一個發佈(Publish)功能來驗證看看囉!
useEffect(()=>{
subMessage(client);
return () => {
client.end();
};
},[])
我們將發佈功能寫成function,並綁定在按鈕的onClick事件裡,每次點擊就發佈一則訊息出去。
而發佈的topic就是我們已訂閱好的「test1103」、message內容則設定為發佈當下的時戳,以便於確認每次發佈的訊息是否皆有收到。
★ message的資料型態必須是「字串」、「Buffer」或「ArrayBuffer」,因此將時戳轉換為字串後再發佈。
const pubMessage = (client) => {
const getTimestamp = new Date().getTime();
client.publish('/test1103', `${getTimestamp}`);
};
<button
type='button'
onClick={()=>{pubMessage(client)}}
>
{'發佈(Publish)'}
</button>
最後就可以來實際點擊測試啦!從下圖可以看到,我們每次發佈與接收到的「主題+訊息」。
import mqtt from 'precompiled-mqtt';
import { useEffect, useState } from 'react';
const MqttDemo = () => {
const client = mqtt.connect('https://test.mosquitto.org:8081/');
const [data, setData] = useState(
{
topic: '-',
message: '-',
}
);
const subMessage = (client) => {
client.subscribe('/test1103');
client.on('message', (topic, payload) => {
const data2String = payload.toString();
setData(
{
topic: topic,
message: JSON.parse(data2String)
}
);
});
};
const pubMessage = (client) => {
const getTimestamp = new Date().getTime();
client.publish('/test1103', `${getTimestamp}`);
};
useEffect(()=>{
subMessage(client);
return () => {
client.end();
};
},[])
return (
<div style={{width: '400px', margin: 'auto'}}>
<h3>
{`topic: ${data.topic}, Message: ${data.message}`}
</h3>
<button
type='button'
onClick={()=>{pubMessage(client)}}
>
{'發佈(Publish)'}
</button>
</div>
)
};
export default MqttDemo;
以上React+MQTT的基本使用差不多就到這邊啦~
不過之前開發過程中,最頭痛的是在搭配Highcharts作時數圖的部分,後續再把相關過程整理上來囉!